/**
 * @file
 *
 * /dev/lport
 *
 * @brief LPORT driver for mil19869x.
 *
 * @author Andrey Mitrofanov avmwww@gmail.com
 */

#include <rtems.h>
#include <rtems/libio.h>
#include <rtems/error.h>
#include <stdio.h>

#include <bsp.h>
#include <bsp/irq-generic.h>
#include <bsp/1986be9x.h>


#include "lport.h"

typedef struct {
  uint32_t select;
  uint32_t tx;
  uint32_t rx;
  uint32_t csr;
  uint32_t irq_mask;
  uint32_t irq_stat;
} mil1986be9x_lport;

#define PM_121_ENA_BIT		0
#define KVV_047_ENA_BIT		1
#define SLAVE2_ENA_BIT		2
#define SLAVE3_ENA_BIT		3

#define CSR_LEN_BIT		0
#define LPORT_ENABLE		(1 << CSR_LEN_BIT)
#define LPORT_DISABLE		(0 << CSR_LEN_BIT)
#define CSR_LTRAN_BIT		1
#define LPORT_TRANSMITTER	(1 << CSR_LTRAN_BIT)
#define CSR_LCLK_BIT		2
#define LPORT_CLK_10MHz		(0 << CSR_LCLK_BIT)
#define LPORT_CLK_20MHz		(1 << CSR_LCLK_BIT)
#define CSR_LSTAT_0_BIT		3
#define CSR_LSTAT_1_BIT		4
#define CSR_LDW_BIT		6
#define LPORT_DATA_WIDTH_4	(0 << CSR_LDW_BIT)
#define LPORT_DATA_WIDTH_8	(1 << CSR_LDW_BIT)
#define CSR_SRQ_TX_BIT		7
#define IRQ_BUFF_NOT_FULL_BIT	0
#define IRQ_BUFF_EMPTY_BIT	1


static char                      lport_devname[16] = "/dev/lport";
static int                       lport_inited = FALSE;
static rtems_device_major_number lport_major;
static rtems_id                  lport_sem;
static rtems_id                  lport_irq_sem;
static int                       lport_opened = FALSE;
static volatile mil1986be9x_lport  *lport_reg; /* FIXME = base address */


static void
lport_irq_on(const rtems_irq_connect_data *irq)
{
  bsp_interrupt_vector_enable(irq->name);
}

static void
lport_irq_off(const rtems_irq_connect_data *irq)
{
  /* bsp_interrupt_vector_disable(irq->name); */
}

static int
lport_irq_isOn(const rtems_irq_connect_data *irq)
{
  return 1;
}

static rtems_isr lport_irq_handler
(
 void *arg
)
{
  uint32_t irq_stat = lport_reg->irq_stat;

  irq_stat &= lport_reg->irq_mask;

  if (irq_stat & IRQ_BUFF_NOT_FULL_BIT)
    rtems_semaphore_release (lport_irq_sem);
  if (irq_stat & IRQ_BUFF_EMPTY_BIT)
    rtems_semaphore_release (lport_irq_sem);
}

static
rtems_device_driver drv_lport_initialize(
  rtems_device_major_number  major,
  rtems_device_minor_number  minor,
  void                      *arg
)
{
  rtems_status_code status;
  rtems_irq_connect_data irq_conn_data = {
    EXT_INT3_IRQn,
    lport_irq_handler,
    (rtems_irq_hdl_param)NULL,
    lport_irq_on,
    lport_irq_off,
    lport_irq_isOn
  };

  status = rtems_semaphore_create (
        rtems_build_name ('L', 'P', 'R', 'd'),
        1,
        RTEMS_BINARY_SEMAPHORE | RTEMS_INHERIT_PRIORITY | RTEMS_PRIORITY,
        RTEMS_NO_PRIORITY,
        &lport_sem);
  if (status != RTEMS_SUCCESSFUL)
    rtems_fatal_error_occurred (status);

 /*
  * create semaphore for IRQ synchronization
  */
  status = rtems_semaphore_create(
    rtems_build_name('L','P','R','i'),
    0,
    RTEMS_FIFO | RTEMS_SIMPLE_BINARY_SEMAPHORE,
    RTEMS_NO_PRIORITY,
    &lport_irq_sem);
  if (status != RTEMS_SUCCESSFUL)
    rtems_fatal_error_occurred (status);

  if (!BSP_install_rtems_irq_handler (&irq_conn_data))
    rtems_panic("cannot install irq handler for LPORT");

  /* Confugure HW */
  lport_reg->csr = LPORT_TRANSMITTER | LPORT_ENABLE | LPORT_CLK_20MHz |
                   LPORT_DATA_WIDTH_8;

  lport_reg->irq_mask = 0;
  lport_reg->irq_stat = lport_reg->irq_stat;

  /*
   * Register the device
   */
  status = rtems_io_register_name(lport_devname, major, minor);
  if (status != RTEMS_SUCCESSFUL)
    rtems_fatal_error_occurred(status);
  printk("Device: %s registered.\n", lport_devname);
  return RTEMS_SUCCESSFUL;
}

static int lport_do_finalize(void)
{
  rtems_status_code status;

  if ( !lport_inited )
    return RTEMS_SUCCESSFUL;

  if (lport_opened) {
    fprintf(stderr,"LPORT device is opened, unable to proceed\n");
    return RTEMS_RESOURCE_IN_USE;
  }
  status = rtems_io_unregister_driver(lport_major);
  if (status != RTEMS_SUCCESSFUL) {
    fprintf(stderr,"Unable to remove LPORT driver\n");
    return status;
  }
  status = (rtems_status_code)unlink(lport_devname);
  if (status != RTEMS_SUCCESSFUL) {
    fprintf(stderr, "Unable to remove lport device node from file system\n");
    return status;
  };

  rtems_semaphore_delete(lport_sem);
  rtems_semaphore_delete(lport_irq_sem);
  fprintf(stderr,"LPORT driver unloaded successfully\n");
  lport_inited=FALSE;
  return RTEMS_SUCCESSFUL;
}

/*
 *  Open entry point
 */

static
rtems_device_driver drv_lport_open(
  rtems_device_major_number major,
  rtems_device_minor_number minor,
  void                    * arg
)
{
  rtems_status_code sc;

  sc = rtems_semaphore_obtain (lport_sem, RTEMS_WAIT, RTEMS_NO_TIMEOUT);
  if (sc != RTEMS_SUCCESSFUL)
    return sc;

  if (lport_opened) {
    rtems_semaphore_release (lport_sem);
    return RTEMS_RESOURCE_IN_USE;
  }

  lport_opened = TRUE;
  rtems_semaphore_release (lport_sem);
  return RTEMS_SUCCESSFUL;
}

/*
 *  Close entry point
 */

static
rtems_device_driver drv_lport_close(
  rtems_device_major_number major,
  rtems_device_minor_number minor,
  void                    * arg
)
{
  rtems_status_code sc;

  sc = rtems_semaphore_obtain (lport_sem, RTEMS_WAIT, RTEMS_NO_TIMEOUT);
  if (sc != RTEMS_SUCCESSFUL)
    return sc;

  lport_opened = FALSE;
  rtems_semaphore_release (lport_sem);
  return RTEMS_SUCCESSFUL;
}


/*
 * read bytes from the lport
 */

static
rtems_device_driver drv_lport_read(
  rtems_device_major_number major,
  rtems_device_minor_number minor,
  void                    * arg
)
{
  rtems_libio_rw_args_t *rw = arg;
  uint32_t rx;
  uint8_t *in = (uint8_t *)rw->buffer;
  uint32_t count = rw->count;
  rtems_status_code sc;
  int i;

  /* Set port direction to read */
  lport_reg->csr &= ~LPORT_TRANSMITTER;

  rw->bytes_moved = 0;
  while (count) {
    while (!(lport_reg->csr & ((1 << CSR_LSTAT_1_BIT) | (1 << CSR_LSTAT_0_BIT)))) {
      /* Busy, wait interupt */
      lport_reg->irq_mask = 1 << IRQ_BUFF_EMPTY_BIT;
      sc = rtems_semaphore_obtain (lport_irq_sem, RTEMS_WAIT, RTEMS_NO_TIMEOUT);
      lport_reg->irq_mask = 0;
      if (sc != RTEMS_SUCCESSFUL)
        return sc;
    }
    /* 32 bit word */
    rx = lport_reg->rx;
    if (count < 4)
      i = count;
    else
      i = 4;

    rw->bytes_moved += i;
    count -= i;
    while (i--) {
      *in++ = (uint8_t)rx;
      rx >>= 8;
    }
  }
  return RTEMS_SUCCESSFUL;
}

/*
 * write bytes to the lport
 */

static
rtems_device_driver drv_lport_write(
  rtems_device_major_number major,
  rtems_device_minor_number minor,
  void                    * arg
)
{
  rtems_libio_rw_args_t *rw = arg;
  uint32_t tx;
  uint8_t *out = (uint8_t *)rw->buffer;
  uint32_t count = rw->count;
  rtems_status_code sc;
  int i;

  /* Set port direction to write */
  lport_reg->csr |= LPORT_TRANSMITTER;

  rw->bytes_moved = 0;
  while (count) {
    while (lport_reg->csr & (1 << CSR_LSTAT_0_BIT)) {
      /* Busy, wait interupt */
      lport_reg->irq_mask = 1 << IRQ_BUFF_NOT_FULL_BIT;
      sc = rtems_semaphore_obtain (lport_irq_sem, RTEMS_WAIT, RTEMS_NO_TIMEOUT);
      lport_reg->irq_mask = 0;
      if (sc != RTEMS_SUCCESSFUL)
        return sc;
    }
    for (i = 0, tx = 0; i < count && i < 4; i++, out++) {
      tx |= *out << (i * 8);
    }
    lport_reg->tx = tx;
    rw->bytes_moved += i;
    count -= i;
  }
  return RTEMS_SUCCESSFUL;
}

/*
 *  IO Control entry point
 */

static
rtems_device_driver drv_lport_control(
  rtems_device_major_number major,
  rtems_device_minor_number minor,
  void                    * arg
)
{
  rtems_libio_ioctl_args_t *args = (rtems_libio_ioctl_args_t*)arg;

  switch (args->command) {
  case LPORT_IOCTL_SELECT_PM_121:
    lport_reg->select = (~(1 << PM_121_ENA_BIT)) & 0xf;
    break;
  case LPORT_IOCTL_SELECT_KVV_047:
    lport_reg->select = (~(1 << KVV_047_ENA_BIT)) & 0xf;
    break;
  case LPORT_IOCTL_SELECT_SLAVE2:
    lport_reg->select = (~(1 << SLAVE2_ENA_BIT)) & 0xf;
    break;
  case LPORT_IOCTL_SELECT_SLAVE3:
    lport_reg->select = (~(1 << SLAVE3_ENA_BIT)) & 0xf;
    break;
  default:
    break;
  }
  return RTEMS_NOT_IMPLEMENTED;
}

static rtems_driver_address_table drv_lport = {
    drv_lport_initialize,
    drv_lport_open,
    drv_lport_close,
    drv_lport_read,
    drv_lport_write,
    drv_lport_control
};


static int lport_do_initialize(void)
{
  if (!lport_inited) {
    if (RTEMS_SUCCESSFUL==rtems_io_register_driver(0, &drv_lport, &lport_major))
      lport_inited=TRUE;
    else
      fprintf(stderr,"WARNING: registering the LPORT driver FAILED\n");
  }
  return lport_inited;
}

int lport_initialize(void)
{
  return lport_do_initialize();
}

int lport_finalize(void)
{
  return lport_do_finalize();
}


